package com.nulldev.util.encoding.compression;

import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.zip.Deflater;
import java.util.zip.ZipOutputStream;

import com.nulldev.util.crypto.encryptAPI.huffman.HuffmanCompressor;
import com.nulldev.util.crypto.encryptAPI.litestring.LiteString;
import com.nulldev.util.encoding.ObjectUtils;
import com.nulldev.util.encoding.compression.dictonary.Dictonary;
import com.nulldev.util.io.FileUtil;
import com.nulldev.util.io.IOUtils;
import com.nulldev.util.io.ZipUtils;
import com.nulldev.util.io.lzma.LZMA;
import com.nulldev.util.io.lzma.LZMAEncoderWrapper;
import com.nulldev.util.io.lzma.LZMAOutputStream;
import com.nulldev.util.io.lzma.lzma.Encoder;
import com.nulldev.util.java.JVMUtil;
import com.nulldev.util.logging.Logger;
import com.nulldev.util.logging.LoggerFactory;
import com.nulldev.util.other.ByteUtils;
import com.nulldev.util.other.CharsetUtil;
import com.nulldev.util.string.StringsUtil;

/**
 * Compresses data to data + compression ID (0-255)
 * @author null
 *
 */
public class smartCompression {
	
	public static final float version = 1.03f;
	
	private final static Logger log = LoggerFactory.getLoggerD(
			JVMUtil.isArg("--smartCompression.debug"), true, true);
	

	public static enum Header {
		UNCOMPRESSED(0, true, true, false, -1),
		Huffman_Native(1, true, false, false, -1),
		Deflator(2, false, false, true, 32 * 1024 * 1024),
		LZMA(3, true, false, false, 64 * 1024 * 1024),
		LiteString(4, false, true, false, -1),
		SMAZ(5, false, true, false, -1),
		GZIP(6, false, true, true, 32 * 1024 * 1024),
		;
		
		private final int id, dc;
		private Header(final int id, final boolean isLossless, final boolean forStrings,
				final boolean requiresDictonary, final int dictonarySize) {
			this.id = id;
			this.dc = dictonarySize;
		}
		
		public int id() {
			return this.id;
		}

		public int dictonarySize() {
			return this.dc;
		}
	}
	
	private static <T> T compress(final Header h, final T toCompress) throws IOException {
		if (toCompress instanceof byte[]) {
			final byte[] toCompressCasted = (byte[]) toCompress;
			if (h == Header.Deflator) {
				final ByteArrayOutputStream baos = new ByteArrayOutputStream();
				final Deflater deflate = new Deflater(Deflater.BEST_COMPRESSION);
				deflate.setDictionary(
						Dictonary.generateEfficentDictonary(h.dictonarySize()));
				deflate.setInput(toCompressCasted);
				deflate.finish();
				byte[] buffer = new byte[2048];
				while (!deflate.finished()) {
					final int count = deflate.deflate(buffer);
					baos.write(buffer, 0, count);
					baos.flush();
				}
				final byte[] out = baos.toByteArray();
				deflate.end();
				baos.close();
				return (T) out;
			}else if (h == Header.LZMA) {
				final Encoder enc = new Encoder();
				enc.setDictionarySize(h.dictonarySize());
				final ByteArrayOutputStream baos = new ByteArrayOutputStream();
				final LZMAOutputStream lzos = new LZMAOutputStream(baos, 
						new LZMAEncoderWrapper(
								enc));
				lzos.write(toCompressCasted);
				lzos.flush();
				final byte[] out = baos.toByteArray();
				baos.close();
				lzos.close();
				return (T) out;
			}else if (h == Header.Huffman_Native) {
				return (T) HuffmanCompressor.compress(toCompressCasted);
			}else {
				throw new RuntimeException("Cannot compress using that header!");
			}
		}else {
			throw new UnsupportedOperationException("Unknown data type provided!");
		}
	}
	
	private static Header getIdealEncoding(final Object obj) {
		if (obj instanceof String) {
			return Header.LiteString;
		}else if (obj instanceof BufferedImage) {
			throw new UnsupportedOperationException("smartImage is not implemented!");
		}else if (obj instanceof byte[]) {
			final byte[] casted = (byte[]) obj;
			if (casted.length > 64000) {
				if (casted.length > (32 * 1024 * 1024)) {
					return Header.Deflator;
				}else {
					return Header.LZMA;
				}
			}else {
				return Header.Huffman_Native;
			}
		}else {
			throw new UnsupportedOperationException("Unknown data type provided!");
		}
	}
}
